﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Media;
using System.Windows;
using Mesuro.Graphing.Utilities;
using System.ComponentModel;
using System.Diagnostics;
using System.Windows.Data;

namespace Mesuro.Graphing {
	/*
			//Raise StyleChanged event on main Thread
			if(((PlotStyle)sender).StyleChanged!=null)System.Windows.Threading.Dispatcher.CurrentDispatcher.BeginInvoke( ((PlotStyle)sender).StyleChanged, sender, e );
	 * */
	public class PlotStyle : FrameworkElement, ICloneable {

		#region LinePen
		public static readonly DependencyProperty LinePenProperty = DependencyProperty.RegisterAttached( "LinePen", typeof( Pen ), typeof( PlotStyle ), new PropertyMetadata( new Pen( Brushes.Red, 1 ), LinePenChangedStatic ) );
		public Pen LinePen {
			get { return (Pen)GetValue( LinePenProperty ); }
			set { SetValue( LinePenProperty, value ); }
		}
		public static Pen GetLinePen( DependencyObject element ) { return (Pen)element.GetValue( LinePenProperty ); }
		public static void SetLinePen( DependencyObject element, Pen value ) { element.SetValue( LinePenProperty, value ); }
		public static void LinePenChangedStatic( DependencyObject sender, DependencyPropertyChangedEventArgs e ) {
			Pen LinePen = (Pen)e.NewValue;
			if (LinePen.Brush is SolidColorBrush) {
				SolidColorBrush brush = (SolidColorBrush)LinePen.Brush;
				if (brush.Color != GetLineColor( sender ))
					SetLineColor( sender, brush.Color );
			} else sender.SetValue( LineColorProperty, null );
			//Raise StyleChanged event on main Thread
			if (((PlotStyle)sender).StyleChanged != null) System.Windows.Threading.Dispatcher.CurrentDispatcher.BeginInvoke( ((PlotStyle)sender).StyleChanged, sender, e );
		}
		#endregion

		#region LineColor
		public static readonly DependencyProperty LineColorProperty = DependencyProperty.RegisterAttached( "LineColor", typeof( Color? ), typeof( PlotStyle ), new PropertyMetadata( Colors.Red, LineColorChangedStatic ) );
		public Color? LineColor {
			get { return (Color?)GetValue( LineColorProperty ); }
			set { SetValue( LineColorProperty, value ); }
		}
		public static Color? GetLineColor( DependencyObject element ) { return (Color?)element.GetValue( LineColorProperty ); }
		public static void SetLineColor( DependencyObject element, Color? value ) { element.SetValue( LineColorProperty, value ); }
		public static void LineColorChangedStatic( DependencyObject sender, DependencyPropertyChangedEventArgs e ) {
			if (e.NewValue == null) return;

			Pen LinePen = GetLinePen( sender );
			if (LinePen.IsFrozen) LinePen = LinePen.Clone();
			if (LinePen.Brush is SolidColorBrush) {
				if (((SolidColorBrush)LinePen.Brush).Color != (Color)e.NewValue) {
					if (LinePen.Brush.IsFrozen)
						LinePen.Brush = new SolidColorBrush( (Color)e.NewValue );
					else
						((SolidColorBrush)LinePen.Brush).Color = (Color)e.NewValue;
				}
			} else
				LinePen.Brush = new SolidColorBrush( (Color)e.NewValue );
			SetLinePen( sender, LinePen );

			Pen MarkPen = GetMarkerOutline( sender );
			if (MarkPen != null) {
				if (MarkPen.IsFrozen) MarkPen = MarkPen.Clone();
				if (MarkPen.Brush is SolidColorBrush) {
					if (((SolidColorBrush)MarkPen.Brush).Color != (Color)e.NewValue) {
						if (MarkPen.Brush.IsFrozen)
							MarkPen.Brush = new SolidColorBrush( (Color)e.NewValue );
						else
							((SolidColorBrush)MarkPen.Brush).Color = (Color)e.NewValue;
					}
				} else
					MarkPen.Brush = new SolidColorBrush( (Color)e.NewValue );
				SetMarkerOutline( sender, MarkPen );
			}
			//Raise StyleChanged event on main Thread
			if (((PlotStyle)sender).StyleChanged != null) System.Windows.Threading.Dispatcher.CurrentDispatcher.BeginInvoke( ((PlotStyle)sender).StyleChanged, sender, e );
		}
		#endregion

		#region Marker
		public static readonly DependencyProperty MarkerProperty = DependencyProperty.RegisterAttached( "Marker", typeof( Geometry ), typeof( PlotStyle ), new FrameworkPropertyMetadata( PlotMarkers.Circle, MarkerChangedStatic ) );
		[TypeConverter( typeof( PlotMarkers.Converter ) )]
		public Geometry Marker {
			get { return (Geometry)GetValue( MarkerProperty ); }
			set { SetValue( MarkerProperty, value ); }
		}
		[TypeConverter( typeof( PlotMarkers.Converter ) )]
		public static Geometry GetMarker( DependencyObject element ) { return (Geometry)element.GetValue( MarkerProperty ); }
		[TypeConverter( typeof( PlotMarkers.Converter ) )]
		public static void SetMarker( DependencyObject element, Geometry value ) { element.SetValue( MarkerProperty, value ); }
		public static void MarkerChangedStatic( DependencyObject sender, DependencyPropertyChangedEventArgs e ) {
			//Raise StyleChanged event on main Thread
			if (((PlotStyle)sender).StyleChanged != null) System.Windows.Threading.Dispatcher.CurrentDispatcher.BeginInvoke( ((PlotStyle)sender).StyleChanged, sender, e );
		}
		#endregion

		#region MarkerOutline
		public static readonly DependencyProperty MarkerOutlineProperty = DependencyProperty.RegisterAttached( "MarkerOutline", typeof( Pen ), typeof( PlotStyle ), new FrameworkPropertyMetadata( null, MarkerOutlineChangedStatic ) );
		public Pen MarkerOutline {
			get {
				Pen ret = (Pen)GetValue( MarkerOutlineProperty );
				if (ret == null)
					//Create new pen with same colour and thickness but ignore all other properties
					ret = MarkerOutline = new Pen( LinePen.Brush, LinePen.Thickness );
				return ret;
			}
			set { SetValue( MarkerOutlineProperty, value ); }
		}
		public static Pen GetMarkerOutline( DependencyObject element ) { return (Pen)element.GetValue( MarkerOutlineProperty ); }
		public static void SetMarkerOutline( DependencyObject element, Pen value ) { element.SetValue( MarkerOutlineProperty, value ); }
		public static void MarkerOutlineChangedStatic( DependencyObject sender, DependencyPropertyChangedEventArgs e ) {
			//Raise StyleChanged event on main Thread
			if (((PlotStyle)sender).StyleChanged != null) System.Windows.Threading.Dispatcher.CurrentDispatcher.BeginInvoke( ((PlotStyle)sender).StyleChanged, sender, e );
		}
		#endregion

		#region MarkerFill
		public static readonly DependencyProperty MarkerFillProperty = DependencyProperty.RegisterAttached( "MarkerFill", typeof( Brush ), typeof( PlotStyle ), new FrameworkPropertyMetadata( Brushes.Transparent, MarkerFillChangedStatic ) );
		public Brush MarkerFill {
			get { return (Brush)GetValue( MarkerFillProperty ); }
			set { SetValue( MarkerFillProperty, value ); }
		}
		public static Brush GetMarkerFill( DependencyObject element ) { return (Brush)element.GetValue( MarkerFillProperty ); }
		public static void SetMarkerFill( DependencyObject element, Brush value ) { element.SetValue( MarkerFillProperty, value ); }
		public static void MarkerFillChangedStatic( DependencyObject sender, DependencyPropertyChangedEventArgs e ) {
			//Raise StyleChanged event on main Thread
			if (((PlotStyle)sender).StyleChanged != null) System.Windows.Threading.Dispatcher.CurrentDispatcher.BeginInvoke( ((PlotStyle)sender).StyleChanged, sender, e );
		}
		#endregion

		#region FillColor
		public static readonly DependencyProperty FillColorProperty = DependencyProperty.RegisterAttached( "FillColor", typeof( Color? ), typeof( PlotStyle ), new PropertyMetadata( Colors.Transparent, FillColorChangedStatic ) );
		public Color? FillColor {
			get { return (Color?)GetValue( FillColorProperty ); }
			set { SetValue( FillColorProperty, value ); }
		}
		public static Color? GetFillColor( DependencyObject element ) { return (Color?)element.GetValue( FillColorProperty ); }
		public static void SetFillColor( DependencyObject element, Color? value ) { element.SetValue( FillColorProperty, value ); }
		public static void FillColorChangedStatic( DependencyObject sender, DependencyPropertyChangedEventArgs e ) {
			if (e.NewValue == null) return;
			Brush MarkerFill = GetMarkerFill( sender );
			if (MarkerFill is SolidColorBrush) {
				if (((SolidColorBrush)MarkerFill).Color == (Color)e.NewValue) return;
				if (MarkerFill.IsFrozen)
					MarkerFill = new SolidColorBrush( (Color)e.NewValue );
				else
					((SolidColorBrush)MarkerFill).Color = (Color)e.NewValue;
			} else
				MarkerFill = new SolidColorBrush( (Color)e.NewValue );
			SetMarkerFill( sender, MarkerFill );
			//Raise StyleChanged event on main Thread
			if (((PlotStyle)sender).StyleChanged != null) System.Windows.Threading.Dispatcher.CurrentDispatcher.BeginInvoke( ((PlotStyle)sender).StyleChanged, sender, e );
		}
		#endregion

		#region MarkerSize
		public static readonly DependencyProperty MarkerSizeProperty = DependencyProperty.RegisterAttached( "MarkerSize", typeof( MarkerSize ), typeof( PlotStyle ), new PropertyMetadata( MarkerSize.Medium, MarkerSizeChangedStatic ) );
		public MarkerSize MarkerSize {
			get { return (MarkerSize)GetValue( MarkerSizeProperty ); }
			set { SetValue( MarkerSizeProperty, value ); }
		}
		public static void MarkerSizeChangedStatic( DependencyObject sender, DependencyPropertyChangedEventArgs e ) {
			//Raise StyleChanged event on main Thread
			if (((PlotStyle)sender).StyleChanged != null) System.Windows.Threading.Dispatcher.CurrentDispatcher.BeginInvoke( ((PlotStyle)sender).StyleChanged, sender, e );
		}
		#endregion

		public event DependencyPropertyChangedEventHandler StyleChanged;


		public void Freeze() {
			LinePen.Freeze();
			Marker.Freeze();
			MarkerOutline.Freeze();
			MarkerFill.Freeze();
		}

		public object Clone() {
			return base.MemberwiseClone();
		}
	}

	[TypeConverter( typeof( MarkerSize.Converter ) )]
	public class MarkerSize {
		public class Converter : System.ComponentModel.TypeConverter {
			private readonly StandardValuesCollection standardValues = new StandardValuesCollection( Values );
			public override bool CanConvertFrom( System.ComponentModel.ITypeDescriptorContext context, Type sourceType ) {
				return sourceType == typeof( string ) || sourceType == typeof( double ) || sourceType == typeof( int ) || base.CanConvertFrom( context, sourceType );
			}
			public override bool CanConvertTo( ITypeDescriptorContext context, Type destinationType ) {
				return destinationType == typeof( string ) || destinationType == typeof( double ) || base.CanConvertTo( context, destinationType );
			}
			public override bool GetStandardValuesSupported( System.ComponentModel.ITypeDescriptorContext context ) {
				return true;
			}
			public override System.ComponentModel.TypeConverter.StandardValuesCollection GetStandardValues( System.ComponentModel.ITypeDescriptorContext context ) {
				return standardValues;
			}
			public override object ConvertFrom( System.ComponentModel.ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value ) {
				if (value is MarkerSize)
					return value;
				else if (value is string) {
					switch ((string)value) {
						case "XXLarge": return XXLarge;
						case "XLarge": return XLarge;
						case "Large": return XLarge;
						case "Medium": return Medium;
						case "Small": return Small;
						case "XSmall": return XSmall;
					}
					double dbl;
					if (double.TryParse( (string)value, out dbl ))
						return new MarkerSize( dbl );
				} else if (value is double || value is int)
					return new MarkerSize( (double)value );
				return base.ConvertFrom( context, culture, value );
			}
			public override object ConvertTo( ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType ) {
				if (destinationType == typeof( string )) {
					if (value == MarkerSize.XXLarge)
						return "XXLarge";
					else if (value == MarkerSize.XLarge)
						return "XLarge";
					else if (value == MarkerSize.Large)
						return "Large";
					else if (value == MarkerSize.Medium)
						return "Medium";
					else if (value == MarkerSize.Small)
						return "Small";
					else if (value == MarkerSize.XSmall)
						return "XSmall";
					else
						return ((MarkerSize)value).value.ToString();
				} else if (destinationType == typeof( double )) {
					return ((MarkerSize)value).value;
				}
				return base.ConvertTo( context, culture, value, destinationType );
			}
		}
		public static readonly MarkerSize XXLarge = new MarkerSize( 25 );
		public static readonly MarkerSize XLarge = new MarkerSize( 10 );
		public static readonly MarkerSize Large = new MarkerSize( 5 );
		public static readonly MarkerSize Medium = new MarkerSize( 3 );
		public static readonly MarkerSize Small = new MarkerSize( 2 );
		public static readonly MarkerSize XSmall = new MarkerSize( 1 );
		public static readonly MarkerSize[] Values = new MarkerSize[] { XXLarge, XLarge, Large, Medium, Small, XSmall };

		public readonly double value;
		private MarkerSize( double value ) {
			this.value = value;
		}

		public double getScaleValue( double dataWidth ) {
			return value * dataWidth;
		}

		public static implicit operator MarkerSize( double pixelValue ) {
			return new MarkerSize( pixelValue );
		}
	}

	public static class PlotMarkers {
		public class Converter : System.ComponentModel.TypeConverter {
			private StandardValuesCollection standardValues = new StandardValuesCollection( new String[] { "None", "Circle", "Cross", "Triangle", "Square", "Diamond", "Circumflex" } );
			public override bool CanConvertFrom( System.ComponentModel.ITypeDescriptorContext context, Type sourceType ) {
				return sourceType == typeof( String ) || base.CanConvertFrom( context, sourceType );
			}
			public override bool GetStandardValuesSupported( System.ComponentModel.ITypeDescriptorContext context ) {
				return true;
			}
			public override System.ComponentModel.TypeConverter.StandardValuesCollection GetStandardValues( System.ComponentModel.ITypeDescriptorContext context ) {
				return standardValues;
			}
			public override object ConvertFrom( System.ComponentModel.ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value ) {
				if (value is string) {
					switch ((string)value) {
						case "None": return None;
						case "Circle": return Circle;
						case "Cross": return Cross;
						case "Triangle": return Triangle;
						case "Square": return Square;
						case "Diamond": return Diamond;
						case "Circumflex": return Circumflex;
					}
				}
				return null;
			}
		}
		public static readonly Geometry[] Values;
		public static readonly Geometry None;
		public static readonly Geometry Circle;
		public static readonly Geometry Cross;
		public static readonly Geometry Triangle;
		public static readonly Geometry Square;
		public static readonly Geometry Diamond;
		public static readonly Geometry Circumflex;

		/// <summary>
		/// Creates the standard nElements markers.
		/// </summary>
		static PlotMarkers() {
			//Circle
			Circle = new EllipseGeometry( new Point( 0, 0 ), 0.6, 0.6 );
			Circle.Freeze();
			//Cross
			Cross = new GeometryGroup();
			GeometryGroup gCross = Cross as GeometryGroup;
			gCross.Children.Add( new LineGeometry( new Point( -0.6, -0.6 ), new Point( 0.6, 0.6 ) ) );
			gCross.Children.Add( new LineGeometry( new Point( -0.6, 0.6 ), new Point( 0.6, -0.6 ) ) );
			Cross.Freeze();
			//Triangle
			Triangle = new StreamGeometry();
			using (StreamGeometryContext cx = ((StreamGeometry)Triangle).Open()) {
				cx.BeginFigure( new Point( 0, -4d / 3 ), true, true );
				cx.LineTo( new Point( -1d, 2d / 3 ), true, true );
				cx.LineTo( new Point( 1d, 2d / 3 ), true, true );
				cx.Close();
			}
			Triangle.Freeze();
			//Square
			Square = new RectangleGeometry( new Rect( -0.5, -0.5, 1, 1 ) );
			Square.Freeze();
			//Diamond
			Diamond = new StreamGeometry();
			using (StreamGeometryContext cx = ((StreamGeometry)Diamond).Open()) {
				cx.BeginFigure( new Point( 0, -1 ), true, true );
				cx.LineTo( new Point( -2d / 3, 0 ), true, true );
				cx.LineTo( new Point( 0, 1 ), true, true );
				cx.LineTo( new Point( 2d / 3, 0 ), true, true );
				cx.Close();
			}
			Diamond.Freeze();
			//Circumflex
			Circumflex = new StreamGeometry();
			using (StreamGeometryContext cx = ((StreamGeometry)Circumflex).Open()) {
				cx.BeginFigure( new Point( 0, 0 ), true, true );
				cx.LineTo( new Point( -1, .5 ), true, true );
				cx.LineTo( new Point( 0, -.5 ), true, true );
				cx.LineTo( new Point( 1, .5 ), true, true );
				cx.LineTo( new Point( 0, 0 ), true, true );
				cx.Close();
			}
			Circumflex.Freeze();
			Values = new Geometry[] { None, Circle, Cross, Triangle, Square, Diamond, Circumflex };
		}
	}
}
